//call函数的模拟


//call函数正常调用
let foo = {
    value: 1
}
function bar() {
    console.log(this.value)
}
bar.call(foo); // 输出结果为1

/*模拟*/
/*
* call方法的核心原理:
* 1.将函数设置为对象属性
* 2.使用该对象调用函数
* 3.删除临时添加的属性
* 4.返回函数执行的结果
* 注意事项:
* thisArg为null或undefined时，this指向全局对象
* 需要处理参数传递
* 函数可能有返回值
* */

//es5版本

//1.通过Function.prototype添加myCall方法,使所有函数可用
Function.prototype.myCall = function (context) {
    //2.处理context形参,如果context为null或undefined,则指向全局对象
    var context = context || window;
    //3.将当前函数(this)设置为context的临时属性fn
    // 这里的this可用理解为调用myCall的函数对象
    context.fn = this;
    //4.获取传入的参数，除了第一个context参数
    const args = [];
    //arguments 是函数内部的一个类数组对象,包含调用函数时传入的所有参数
    for (let i = 1; i < arguments.length; i++) {
        args.push('arguments[' + i + ']');
    }
    //5.执行函数，使用eval函数拼接参数
    const result = eval('context.fn(' + args + ')');
    //6.删除临时添加的属性
    delete context.fn;
    //7.返回指向的结果
    return result;
}

//示例解析：
function greet(name) {
    console.log(`hello,${name}! I am ${this.title}`);
}
const person = {
    title: 'LH'
}
greet.myCall(person, 'John');  //hello,John! I am LH

/*
* 当调用greet.myCall(person,'John');时
* myCall内部的this指向 当调用greet函数
* context指向 person 对象
* context.fn = this; ===>  person.fn = greet; ===> person.fn('John');
* 此时fn中的this指向person
* */


// es6版本
//1..通过Function.prototype添加myCallES6方法,使所有函数可用
Function.prototype.myCallES6 = function (context, ...args) {
    //2.处理上下文参数,如果 context 为 null 或 undefined，默认指向全局对象
    context = context || window;
    // 3.根据环境选择属性名策略
    const useSymbol = typeof Symbol !== 'undefined';
    /*
    *4.检测当前环境是否支持 Symbol
    * 现代浏览器/Node.js: Symbol 已定义 → useSymbol = true,支持 Symbol: 创建唯一Symbol → Symbol()
    * 老旧环境(如IE): Symbol 未定义 → useSymbol = false,不支持 Symbol: 生成随机字符串 → `__fn_${时间戳}_${随机数}`
    * */
    const propName = useSymbol ? Symbol() : `_fn_${Date.now()}_${Math.random()}`;
    /*
    * 5.将当前函数绑定到上下文,同ES5版本一样即设置当前函数(this)为context的临时属性propName
    * 这里的this可用理解为调用myCallES6的函数对象
    * */
    context[propName] = this;
    //6.直接执行函数
    const result = context[propName](...args);
    //7.清理临时属性
    delete context[propName];
    //8.返回结果
    return result;
}

/*相同示例解析ES6版本call函数模拟*/
greet.myCallES6(person, 'John'); //hello,John! I am LH
/*
* person ===> context
* args ===> ['John']
* propName = Symbol() 或 随机字符串
* this ===> greet函数
*  person[propName] ===> greet函数
* 执行 person[propName]('John')
*  result = 'hello,John! I am LH'
* */

